import numpy as np

class NumericalGradient:
    #TODO 我们来考虑一下需要什么东西
    #一个loss函数必不可少，根据loss我们才能计算梯度
    #所有自变量也是必须的，我们待调整的就是这些数值
    #还有从自变量得到因变量的函数映射关系
    #还需要完整数据集的阅读权限。如果我们从最原始的角度考虑问题————梯度下降是要用全数据集的
    #还有数值梯度下降的步长，其实就是学习率
    def __init__(self):
        #这里的model是个对象，lossfuntion是个函数指针,step是步长
        print("梯度下降器构造成功！")
        #TODO 我觉得怎么去动态调整步长也很有学问，优化器源码怎么实现来着？

    #TODO 这里应该要获取所有自变量进行变动后的梯度，枚举？
    def calgradient(self,model,lossfuntion,step):
        #paras是模型的参数量
        count = 3 ** model.paranum
        tempmodel = []
        for i in range(count + 1):
            tempmodel.append(self.changepara(i,model,step))

        losses = []
        #TODO 这里把loss想的太简单了，loss不只是输入一个模型就能解决的问题
        #或者我们可以把loss封装起来，
        for i in range(tempmodel.__len__()):
            losses.append(lossfuntion(tempmodel[i]))
        #然后计算下降最快的方向？
        minloss = losses[0]
        minptr = 0
        for i in range(losses.__len__()):
            if(minloss > losses[i]):
                minloss = losses[i]
                minptr = i

        '这里可以加个output函数打印一下中间变量看看运行是否正确'
        #然后下降
        return tempmodel[minptr], minloss[minptr]


    #构造一个映射，使得其能够正确的被扰动
    def changepara(self,num,model,step):
        #第i个参数被扰动step的距离
        tempmodel = model
        for i in range(model.paranum):
            #tempmodel.para[0,i] += (num % 3 - 1) * self.step
            temp = tempmodel.getpara(0,i) + (num % 3 - 1) * step
            #如果这里我们做过归一化，其实应该要对temp的边界值做出取舍
            tempmodel.setpara(0,i,temp)
            num = num / 3
        return tempmodel